<template>
  <div class="control-mode">
    <div class="header">
      <div class="header-left">
        <button class="back-btn" @click="goBack">← 返回</button>
        <h1>操控模式</h1>
      </div>
      <div class="header-right">
        <div class="connection-status">
          <span :class="connectionIndicatorClass"></span>
          <span class="status-text">{{ connectionStatusText }}</span>
        </div>
        <button class="config-btn" @click="openConfigModal">
          <i class="fa fa-cog"></i>
          配置
        </button>
        <button 
          class="connect-btn" 
          :class="connectButtonClass"
          @click="toggleConnection"
        >
          <i :class="connectButtonIcon"></i>
          {{ connectButtonText }}
        </button>
      </div>
    </div>
    
    <div class="control-container">
      <!-- 图像流显示区域 -->
      <div class="video-panel">
        <h3>车载摄像头</h3>
        <div class="video-container">
          <!-- 使用iframe来避免CORS问题 -->
          <iframe 
            v-if="useIframe"
            :src="streamUrl" 
            class="video-iframe"
            frameborder="0"
            @load="handleIframeLoad"
            @error="handleIframeError"
          ></iframe>
          
          <!-- 备用：直接图像加载 -->
          <img 
            v-else
            :src="streamUrl" 
            alt="车载摄像头图像流"
            class="video-stream"
            @error="handleImageError"
            @load="handleImageLoad"
            crossorigin="anonymous"
          />
          
          <div v-if="!imageLoaded" class="loading-overlay">
            <div class="loading-spinner"></div>
            <p>正在连接摄像头...</p>
            <button class="retry-btn" @click="retryConnection">重试连接</button>
            <button class="switch-mode-btn" @click="switchDisplayMode">
              {{ useIframe ? '切换到图像模式' : '切换到iframe模式' }}
            </button>
          </div>
        </div>
        
        <!-- 连接状态和控制 -->
        <div class="connection-controls">
          <div class="url-input">
            <label>图像流地址:</label>
            <input 
              v-model="streamUrl" 
              type="text" 
              class="url-field"
              @keyup.enter="updateStreamUrl"
            />
            <button @click="updateStreamUrl" class="update-btn">更新</button>
          </div>
        </div>
      </div>
      
      <!-- 控制面板 -->
      <div class="control-panel">
        <h3>车辆控制</h3>
        
        <!-- ROS2方向键控制 -->
        <div class="ros-controller">
          <h4>ROS2方向键控制</h4>
          <div class="direction-controls-grid">
            <div class="control-row">
              <div></div>
              <button 
                class="ros-direction-btn" 
                @mousedown="setLinearVelocity(linearSpeed)"
                @mouseup="stopMovement"
                @touchstart="setLinearVelocity(linearSpeed)"
                @touchend="stopMovement"
              >
                ↑ 前进
              </button>
              <div></div>
            </div>
            <div class="control-row">
              <button 
                class="ros-direction-btn" 
                @mousedown="setAngularVelocity(angularSpeed)"
                @mouseup="stopMovement"
                @touchstart="setAngularVelocity(angularSpeed)"
                @touchend="stopMovement"
              >
                ← 左转
              </button>
              <button 
                class="ros-direction-btn stop" 
                @click="sendStopCommand"
              >
                停止
              </button>
              <button 
                class="ros-direction-btn" 
                @mousedown="setAngularVelocity(-angularSpeed)"
                @mouseup="stopMovement"
                @touchstart="setAngularVelocity(-angularSpeed)"
                @touchend="stopMovement"
              >
                右转 →
              </button>
            </div>
            <div class="control-row">
              <div></div>
              <button 
                class="ros-direction-btn" 
                @mousedown="setLinearVelocity(-linearSpeed)"
                @mouseup="stopMovement"
                @touchstart="setLinearVelocity(-linearSpeed)"
                @touchend="stopMovement"
              >
                ↓ 后退
              </button>
              <div></div>
            </div>
          </div>
        </div>
        
        <!-- 虚拟摇杆控制 -->
        <div class="joystick-controller">
          <h4>虚拟摇杆控制</h4>
          <div 
            class="joystick-area" 
            ref="joystick"
            @mousedown="startJoystick"
            @touchstart="startJoystick"
          >
            <div 
              class="joystick-knob" 
              ref="joystickKnob"
            ></div>
          </div>
        </div>
        
        <!-- 键盘控制说明 -->
        <div class="keyboard-controller">
          <h4>键盘WASD控制</h4>
          <div class="keyboard-instructions">
            <div class="key-instruction">
              <span class="key-badge">W</span>
              <span class="key-description">前进</span>
            </div>
            <div class="key-instruction">
              <span class="key-badge">S</span>
              <span class="key-description">后退</span>
            </div>
            <div class="key-instruction">
              <span class="key-badge">A</span>
              <span class="key-description">左转</span>
            </div>
            <div class="key-instruction">
              <span class="key-badge">D</span>
              <span class="key-description">右转</span>
            </div>
            <div class="key-instruction">
              <span class="key-badge">空格</span>
              <span class="key-description">停止</span>
            </div>
          </div>
          <p class="keyboard-note">💡 提示：确保焦点在页面上，避免在输入框中使用键盘控制</p>
        </div>
        
        <!-- 手柄控制说明 -->
        <div class="gamepad-controller">
          <h4>手柄控制</h4>
          <div class="gamepad-instructions">
            <div class="gamepad-instruction">
              <span class="gamepad-badge">左摇杆/方向键</span>
              <span class="gamepad-description">移动控制</span>
            </div>
            <div class="gamepad-instruction">
              <span class="gamepad-badge">A按钮</span>
              <span class="gamepad-description">连接/断开</span>
            </div>
            <div class="gamepad-instruction">
              <span class="gamepad-badge">B按钮</span>
              <span class="gamepad-description">停止移动</span>
            </div>
            <div class="gamepad-instruction">
              <span class="gamepad-badge">Y按钮</span>
              <span class="gamepad-description">返回首页</span>
            </div>
          </div>
          <p class="gamepad-note" v-if="gamepadConnected">🎮 手柄已连接，可以使用手柄控制</p>
        </div>
        
        <!-- 速度控制 -->
        <div class="speed-controls">
          <h4>速度控制</h4>
          <div class="speed-control">
            <label>线性速度: <span class="speed-value">{{ linearSpeed.toFixed(1) }}</span> m/s</label>
            <input 
              type="range" 
              v-model="linearSpeed" 
              min="0" 
              max="1" 
              step="0.1"
              class="speed-slider"
            />
          </div>
          <div class="speed-control">
            <label>角速度: <span class="speed-value">{{ angularSpeed.toFixed(1) }}</span> rad/s</label>
            <input 
              type="range" 
              v-model="angularSpeed" 
              min="0" 
              max="2" 
              step="0.1"
              class="speed-slider"
            />
          </div>
        </div>
        
        <!-- 原有控制按钮 -->
        <div class="control-buttons">
          <button 
            class="control-btn primary"
            @click="sendControlCommand"
          >
            发送控制指令
          </button>
        </div>
        
        <!-- 状态信息 -->
        <div class="status-info">
          <div class="status-item">
            <span class="status-label">摄像头状态:</span>
            <span class="status-value" :class="imageLoaded ? 'connected' : 'disconnected'">
              {{ imageLoaded ? '已连接' : '未连接' }}
            </span>
          </div>
          <div class="status-item">
            <span class="status-label">小车IP:</span>
            <span class="status-value">{{ config.robotIP }}</span>
          </div>
          <div class="status-item">
            <span class="status-label">WebSocket状态:</span>
            <span class="status-value" :class="isConnected ? 'connected' : 'disconnected'">
              {{ isConnected ? '已连接' : '未连接' }}
            </span>
          </div>
          <div class="status-item">
            <span class="status-label">当前指令:</span>
            <span class="status-value" :class="currentCommandClass">
              {{ currentCommandText }}
            </span>
          </div>
          <div class="status-item">
            <span class="status-label">发送频率:</span>
            <span class="status-value">{{ updateFrequency }} Hz</span>
          </div>
          <div class="status-item">
            <span class="status-label">线性速度:</span>
            <span class="status-value">{{ linearSpeed.toFixed(1) }} m/s</span>
          </div>
          <div class="status-item">
            <span class="status-label">角速度:</span>
            <span class="status-value">{{ angularSpeed.toFixed(1) }} rad/s</span>
          </div>
          <div class="status-item">
            <span class="status-label">手柄状态:</span>
            <span class="status-value" :class="gamepadConnected ? 'connected' : 'disconnected'">
              {{ gamepadConnected ? '已连接' : '未连接' }}
            </span>
          </div>
          <div class="status-item">
            <span class="status-label">地图监控:</span>
            <span class="status-value" :class="mapMonitorConnected ? 'connected' : 'disconnected'">
              {{ mapMonitorConnected ? '已连接' : '未连接' }}
            </span>
          </div>
          <div class="status-item">
            <span class="status-label">监控地址:</span>
            <span class="status-value">{{ config.mapMonitorIP }}:{{ config.mapMonitorPort }}</span>
          </div>
          <div class="status-item" v-if="lastMapUpdateTime">
            <span class="status-label">最后更新:</span>
            <span class="status-value">{{ formatTime(lastMapUpdateTime) }}</span>
          </div>
        </div>
        
        <!-- 地图显示 -->
        <div class="map-panel">
          <h4>建图显示</h4>
          <div class="map-container">
            <div class="map-display">
              <canvas 
                ref="mapCanvas" 
                class="map-canvas"
                :width="mapCanvasWidth"
                :height="mapCanvasHeight"
              ></canvas>
              <div v-if="!mapLoaded" class="map-loading">
                <div class="loading-spinner"></div>
                <p>正在加载地图...</p>
              </div>
            </div>
            <div class="map-info">
              <div class="map-detail">
                <span class="map-label">分辨率:</span>
                <span class="map-value">{{ mapResolution }} m/pixel</span>
              </div>
              <div class="map-detail">
                <span class="map-label">原点:</span>
                <span class="map-value">[{{ mapOrigin.join(', ') }}]</span>
              </div>
              <div class="map-detail">
                <span class="map-label">尺寸:</span>
                <span class="map-value">{{ mapWidth }} × {{ mapHeight }}</span>
              </div>
              <div class="map-detail">
                <span class="map-label">状态:</span>
                <span class="map-value" :class="mapLoaded ? 'connected' : 'disconnected'">
                  {{ mapLoaded ? '已加载' : '未加载' }}
                </span>
              </div>
            </div>
            <div class="map-controls">
              <div class="map-selector">
                <label for="mapSelect">当前地图:</label>
                <select 
                  id="mapSelect" 
                  v-model="currentMapName" 
                  @change="switchToMap(currentMapName)"
                  class="map-select"
                >
                  <option v-for="mapName in availableMaps" :key="mapName" :value="mapName">
                    {{ mapName }}
                  </option>
                </select>
              </div>
              <div class="map-monitor-control">
                <label class="monitor-switch">
                  <input 
                    type="checkbox" 
                    v-model="autoReloadMap" 
                    @change="toggleMapMonitor"
                  />
                  <span class="switch-slider"></span>
                  <span class="switch-label">自动更新地图</span>
                </label>
              </div>
              <button @click="loadMap" class="map-btn" :disabled="mapLoading">
                {{ mapLoading ? '加载中...' : '重新加载地图' }}
              </button>
              <button @click="resetMapView" class="map-btn">
                重置视图
              </button>
              <button @click="showMapSelector" class="map-btn">
                上传地图
              </button>
              <input 
                ref="mapFileInput" 
                type="file" 
                accept=".pgm,.yaml,.yml" 
                multiple 
                @change="handleMapFileUpload" 
                style="display: none;"
              />
            </div>
          </div>
        </div>

        <!-- 操作日志 -->
        <div class="log-panel">
          <h4>操作日志</h4>
          <div class="log-container">
            <div 
              v-for="(log, index) in logs" 
              :key="index" 
              class="log-item"
              :class="log.type"
            >
              <span class="log-time">{{ log.time }}</span>
              <span class="log-message">{{ log.message }}</span>
            </div>
          </div>
        </div>
      </div>
    </div>
    
    <!-- IP配置弹窗 -->
    <div v-if="showConfigModal" class="modal" @click="closeConfigModal">
      <div class="modal-content" @click.stop>
        <div class="modal-header">
          <h3>IP配置</h3>
          <button class="close" @click="closeConfigModal">&times;</button>
        </div>
        <div class="modal-body">
          <div class="config-group">
            <label>机器人IP地址:</label>
            <input v-model="tempConfig.robotIP" type="text" placeholder="192.168.149.1" />
          </div>
          <div class="config-group">
            <label>ROSBridge端口:</label>
            <input v-model="tempConfig.rosbridgePort" type="number" placeholder="9090" />
          </div>
          <div class="config-group">
            <label>视频流端口:</label>
            <input v-model="tempConfig.streamPort" type="number" placeholder="8080" />
          </div>
          <div class="config-group">
            <label>控制话题:</label>
            <input v-model="tempConfig.cmdVelTopic" type="text" placeholder="/cmd_vel" />
          </div>
          <div class="config-group">
            <label>地图监控IP:</label>
            <input v-model="tempConfig.mapMonitorIP" type="text" placeholder="localhost" />
          </div>
          <div class="config-group">
            <label>地图监控端口:</label>
            <input v-model="tempConfig.mapMonitorPort" type="number" placeholder="8000" />
          </div>
        </div>
        <div class="modal-footer">
          <button class="btn-cancel" @click="closeConfigModal">取消</button>
          <button class="btn-save" @click="saveConfig">保存</button>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'ControlMode',
  data() {
    return {
      // 统一IP配置
      config: {
        robotIP: '192.168.149.1',
        rosbridgePort: 9090,
        streamPort: 8080,
        cmdVelTopic: '/cmd_vel',
        mapMonitorIP: 'localhost',
        mapMonitorPort: 8000
      },
      
      // 图像流相关
      imageLoaded: false,
      useIframe: true,
      
      // ROS2连接相关
      updateFrequency: 10,
      websocket: null,
      isConnected: false,
      
      // 速度控制
      linearSpeed: 0.5,
      angularSpeed: 0.8,
      currentLinearX: 0,
      currentAngularZ: 0,
      
      // 摇杆控制
      joystickActive: false,
      
      // 键盘控制状态跟踪
      keyboardState: {
        w: false,
        s: false,
        a: false,
        d: false
      },
      
      // 定时器
      sendInterval: null,
      
      // 日志
      logs: [],
      
      // Gamepad控制相关
      gamepadConnected: false,
      gamepadIndex: null,
      gamepadAnimationFrame: null,
      lastButtonStates: {},
      deadZone: 0.2, // 摇杆死区，降低灵敏度
      lastMoveTime: 0,
      moveInterval: 50, // 移动间隔时间(ms)，提高响应性
      lastGamepadInput: false, // 跟踪手柄上一次是否有输入
      
      // 地图相关
      mapLoaded: false,
      mapLoading: false,
      mapResolution: 0.05,
      mapOrigin: [-1.5, -1.44, 0],
      currentMapName: 'map_01',
      availableMaps: ['map_01'],
      uploadedMaps: new Map(), // 存储上传的地图文件
      mapWidth: 76,
      mapHeight: 80,
      mapCanvasWidth: 380,
      mapCanvasHeight: 400,
      mapImageData: null,
      mapScale: 1,
      mapOffsetX: 0,
      mapOffsetY: 0,
      
      // 地图监控相关
      mapMonitorWebSocket: null,
      mapMonitorConnected: false,
      autoReloadMap: true,
      lastMapUpdateTime: null,
      
      // IP配置弹窗相关
      showConfigModal: false,
      tempConfig: {
        robotIP: '',
        rosbridgePort: 9090,
        streamPort: 8080,
        cmdVelTopic: '/cmd_vel',
        mapMonitorIP: 'localhost',
        mapMonitorPort: 8000
      }
    }
  },
  computed: {
    connectionIndicatorClass() {
      return {
        'connection-indicator': true,
        'connected': this.isConnected,
        'disconnected': !this.isConnected
      }
    },
    connectionStatusText() {
      return this.isConnected ? '已连接' : '未连接'
    },
    connectButtonClass() {
      return {
        'connect-btn': true,
        'connected': this.isConnected,
        'disconnected': !this.isConnected
      }
    },
    connectButtonText() {
      return this.isConnected ? '断开连接' : '连接小车'
    },
    connectButtonIcon() {
      return this.isConnected ? 'fa fa-unplug' : 'fa fa-plug'
    },
    currentCommandText() {
      if (this.currentLinearX === 0 && this.currentAngularZ === 0) {
        return '停止'
      }
      let commandText = ''
      if (this.currentLinearX > 0) commandText += '前进 '
      if (this.currentLinearX < 0) commandText += '后退 '
      if (this.currentAngularZ > 0) commandText += '左转 '
      if (this.currentAngularZ < 0) commandText += '右转 '
      return commandText.trim()
    },
    currentCommandClass() {
      return {
        'moving': this.currentLinearX !== 0 || this.currentAngularZ !== 0,
        'stopped': this.currentLinearX === 0 && this.currentAngularZ === 0
      }
    },
    mapMonitorUrl() {
      return `ws://${this.config.mapMonitorIP}:${this.config.mapMonitorPort}/ws`
    },
    streamUrl() {
      return `http://${this.config.robotIP}:${this.config.streamPort}/stream_viewer?topic=/depth_cam/rgb/image_raw`
    }
  },
  mounted() {
    this.addLog('系统启动', 'info')
    this.addLog(`尝试连接摄像头: ${this.streamUrl}`, 'info')
    this.addLog(`ROS2小车IP: ${this.config.robotIP}`, 'info')
    
    // 添加全局事件监听
    document.addEventListener('mousemove', this.moveJoystick)
    document.addEventListener('mouseup', this.stopJoystick)
    document.addEventListener('touchmove', this.moveJoystick)
    document.addEventListener('touchend', this.stopJoystick)
    
    // 添加键盘事件监听器
    document.addEventListener('keydown', this.handleKeyDown)
    document.addEventListener('keyup', this.handleKeyUp)
    
    // 页面失去焦点时重置键盘状态
    window.addEventListener('blur', this.resetKeyboardState)
    window.addEventListener('focus', this.resetKeyboardState)
    
    // 页面关闭时发送停止指令
    window.addEventListener('beforeunload', this.sendStopCommand)
    
    // 初始化Gamepad
    this.initGamepad()
    
    // 连接地图监控服务
    this.connectMapMonitor()
    
    // 加载地图
    this.loadMap()
  },
  beforeUnmount() {
    // 清理事件监听和连接
    document.removeEventListener('mousemove', this.moveJoystick)
    document.removeEventListener('mouseup', this.stopJoystick)
    document.removeEventListener('touchmove', this.moveJoystick)
    document.removeEventListener('touchend', this.stopJoystick)
    
    // 清理键盘事件监听器
    document.removeEventListener('keydown', this.handleKeyDown)
    document.removeEventListener('keyup', this.handleKeyUp)
    
    // 清理窗口事件监听器
    window.removeEventListener('blur', this.resetKeyboardState)
    window.removeEventListener('focus', this.resetKeyboardState)
    window.removeEventListener('beforeunload', this.sendStopCommand)
    
    this.disconnectFromRobot()
    this.disconnectGamepad()
    this.disconnectMapMonitor()
  },
  methods: {
    // IP配置相关方法
    openConfigModal() {
      // 复制当前配置到临时配置
      this.tempConfig = { ...this.config }
      this.showConfigModal = true
    },
    
    closeConfigModal() {
      this.showConfigModal = false
    },
    
    saveConfig() {
      // 验证IP地址格式
      const ipRegex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
      if (!ipRegex.test(this.tempConfig.robotIP)) {
        this.addLog('机器人IP地址格式不正确', 'error')
        return
      }
      if (!ipRegex.test(this.tempConfig.mapMonitorIP) && this.tempConfig.mapMonitorIP !== 'localhost') {
        this.addLog('地图监控IP地址格式不正确', 'error')
        return
      }
      
      // 检查配置是否有变化
      const configChanged = JSON.stringify(this.config) !== JSON.stringify(this.tempConfig)
      
      if (configChanged) {
        // 保存配置
        this.config = { ...this.tempConfig }
        
        // 如果当前已连接，需要重新连接
        if (this.isConnected) {
          this.addLog('配置已更新，正在重新连接ROS2...', 'info')
          this.disconnectFromRobot()
          setTimeout(() => {
            this.connectToRobot()
          }, 1000)
        }
        
        // 如果地图监控已连接，需要重新连接
        if (this.mapMonitorConnected) {
          this.addLog('地图监控配置已更新，正在重新连接...', 'info')
          this.disconnectMapMonitor()
          setTimeout(() => {
            this.connectMapMonitor()
          }, 1000)
        }
        
        this.addLog('配置已保存', 'success')
      } else {
        this.addLog('配置无变化', 'info')
      }
      
      this.closeConfigModal()
    },
    

    
    goBack() {
      this.$router.push('/')
    },
    
    // 图像流相关方法
    handleImageLoad() {
      this.imageLoaded = true
      this.addLog('摄像头连接成功 (图像模式)', 'success')
    },
    
    handleImageError() {
      this.imageLoaded = false
      this.addLog('摄像头连接失败 (图像模式) - 可能是CORS问题', 'error')
    },
    
    handleIframeLoad() {
      this.imageLoaded = true
      this.addLog('摄像头连接成功 (iframe模式)', 'success')
    },
    
    handleIframeError() {
      this.imageLoaded = false
      this.addLog('摄像头连接失败 (iframe模式)', 'error')
    },
    
    retryConnection() {
      this.imageLoaded = false
      this.addLog('重试连接摄像头...', 'info')
      this.$forceUpdate()
    },
    
    switchDisplayMode() {
      this.useIframe = !this.useIframe
      this.imageLoaded = false
      this.addLog(`切换到${this.useIframe ? 'iframe' : '图像'}模式`, 'info')
    },
    
    updateStreamUrl() {
      this.imageLoaded = false
      this.addLog(`更新图像流地址: ${this.streamUrl}`, 'info')
    },
    
    // ROS2连接相关方法
    toggleConnection() {
      if (this.isConnected) {
        this.disconnectFromRobot()
      } else {
        this.connectToRobot()
      }
    },
    
    connectToRobot() {
      const websocketUrl = `ws://${this.config.robotIP}:${this.config.rosbridgePort}`
      this.websocket = new WebSocket(websocketUrl)
      
      this.websocket.onopen = () => {
        console.log('WebSocket连接已建立')
        this.isConnected = true
        this.addLog('WebSocket连接已建立', 'success')
        this.startSendingCommands()
      }
      
      this.websocket.onclose = () => {
        console.log('WebSocket连接已关闭')
        this.isConnected = false
        this.addLog('WebSocket连接已关闭', 'warning')
        this.stopSendingCommands()
        // 自动重连
        setTimeout(() => {
          if (!this.isConnected) {
            this.addLog('尝试自动重连...', 'info')
            this.connectToRobot()
          }
        }, 3000)
      }
      
      this.websocket.onerror = (error) => {
        console.error('WebSocket错误:', error)
        this.isConnected = false
        this.addLog('WebSocket连接错误', 'error')
      }
      
      this.websocket.onmessage = (event) => {
        console.log('收到消息:', event.data)
      }
    },
    
    disconnectFromRobot() {
      if (this.websocket) {
        this.websocket.close()
        this.websocket = null
      }
      this.isConnected = false
      this.stopSendingCommands()
      this.addLog('已断开WebSocket连接', 'info')
    },
    
    // 控制指令发送
    startSendingCommands() {
      if (this.sendInterval) {
        clearInterval(this.sendInterval)
      }
      
      const intervalMs = 1000 / this.updateFrequency
      this.sendInterval = setInterval(this.sendVelocityCommand, intervalMs)
    },
    
    stopSendingCommands() {
      if (this.sendInterval) {
        clearInterval(this.sendInterval)
        this.sendInterval = null
      }
      this.sendStopCommand()
    },
    
    sendVelocityCommand() {
      if (!this.isConnected || !this.websocket) return
      
      const twistMessage = {
        op: 'publish',
        topic: this.config.cmdVelTopic,
        msg: {
          linear: {
            x: this.currentLinearX,
            y: 0,
            z: 0
          },
          angular: {
            x: 0,
            y: 0,
            z: this.currentAngularZ
          }
        }
      }
      
      try {
        this.websocket.send(JSON.stringify(twistMessage))
      } catch (error) {
        console.error('发送消息失败:', error)
        this.addLog('发送控制指令失败', 'error')
      }
    },
    
    // 运动控制方法
    setLinearVelocity(velocity) {
      this.currentLinearX = velocity
    },
    
    setAngularVelocity(velocity) {
      this.currentAngularZ = velocity
    },
    
    stopMovement() {
      this.currentLinearX = 0
      this.currentAngularZ = 0
    },
    
    sendStopCommand() {
      this.currentLinearX = 0
      this.currentAngularZ = 0
      this.sendVelocityCommand()
    },
    
    // 虚拟摇杆控制
    startJoystick(event) {
      event.preventDefault()
      this.joystickActive = true
      this.$refs.joystickKnob.classList.add('scale-110')
      this.moveJoystick(event.touches ? event.touches[0] : event)
    },
    
    moveJoystick(event) {
      if (!this.joystickActive) return
      
      const joystick = this.$refs.joystick
      const knob = this.$refs.joystickKnob
      
      if (!joystick || !knob) return
      
      const rect = joystick.getBoundingClientRect()
      const centerX = rect.width / 2
      const centerY = rect.height / 2
      const maxDistance = centerX * 0.8
      
      const clientX = event.touches ? event.touches[0].clientX : event.clientX
      const clientY = event.touches ? event.touches[0].clientY : event.clientY
      
      const x = clientX - rect.left - centerX
      const y = clientY - rect.top - centerY
      
      const distance = Math.sqrt(x * x + y * y)
      
      let limitedX = x
      let limitedY = y
      if (distance > maxDistance) {
        limitedX = (x / distance) * maxDistance
        limitedY = (y / distance) * maxDistance
      }
      
      knob.style.transform = `translate(${limitedX}px, ${limitedY}px)`
      
      const normalizedX = limitedX / maxDistance
      const normalizedY = limitedY / maxDistance
      
      this.currentLinearX = -normalizedY * this.linearSpeed
      this.currentAngularZ = normalizedX * this.angularSpeed
    },
    
    stopJoystick() {
      if (this.joystickActive) {
        this.joystickActive = false
        const knob = this.$refs.joystickKnob
        if (knob) {
          knob.classList.remove('scale-110')
          knob.style.transform = 'translate(-50%, -50%)'
        }
        this.currentLinearX = 0
        this.currentAngularZ = 0
      }
    },
    
    // 键盘WASD控制
    handleKeyDown(event) {
      // 防止在输入框中触发
      if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') {
        return
      }
      
      const key = event.key.toLowerCase()
      
      // 如果按键已经按下，不重复处理
      if (this.keyboardState[key]) {
        return
      }
      
      switch (key) {
        case 'w':
          this.keyboardState.w = true
          this.updateKeyboardMovement()
          this.addLog('键盘控制: 开始前进 (W)', 'info')
          break
        case 's':
          this.keyboardState.s = true
          this.updateKeyboardMovement()
          this.addLog('键盘控制: 开始后退 (S)', 'info')
          break
        case 'a':
          this.keyboardState.a = true
          this.updateKeyboardMovement()
          this.addLog('键盘控制: 开始左转 (A)', 'info')
          break
        case 'd':
          this.keyboardState.d = true
          this.updateKeyboardMovement()
          this.addLog('键盘控制: 开始右转 (D)', 'info')
          break
        case ' ': // 空格键停止
          event.preventDefault()
          this.resetKeyboardState()
          this.sendStopCommand()
          this.addLog('键盘控制: 紧急停止 (空格)', 'warning')
          break
      }
    },
    
    handleKeyUp(event) {
      // 防止在输入框中触发
      if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') {
        return
      }
      
      const key = event.key.toLowerCase()
      
      // 松开按键时更新状态
      if (['w', 's', 'a', 'd'].includes(key)) {
        this.keyboardState[key] = false
        this.updateKeyboardMovement()
        this.addLog(`键盘控制: 松开 ${key.toUpperCase()} 键`, 'info')
      }
    },
    
    // 根据键盘状态更新运动
    updateKeyboardMovement() {
      let linearX = 0
      let angularZ = 0
      
      // 计算线性速度 (前进/后退)
      if (this.keyboardState.w) {
        linearX += this.linearSpeed
      }
      if (this.keyboardState.s) {
        linearX -= this.linearSpeed
      }
      
      // 计算角速度 (左转/右转)
      if (this.keyboardState.a) {
        angularZ += this.angularSpeed
      }
      if (this.keyboardState.d) {
        angularZ -= this.angularSpeed
      }
      
      // 更新当前速度
      this.currentLinearX = linearX
      this.currentAngularZ = angularZ
    },
    
    // 重置键盘状态
    resetKeyboardState() {
      this.keyboardState = {
        w: false,
        s: false,
        a: false,
        d: false
      }
      this.currentLinearX = 0
      this.currentAngularZ = 0
    },
    
    // 原有方法
    sendControlCommand() {
      console.log('hello')
      this.addLog('发送控制指令: hello', 'success')
    },
    
    addLog(message, type = 'info') {
      const now = new Date()
      const time = now.toLocaleTimeString()
      this.logs.unshift({ time, message, type })
      
      if (this.logs.length > 50) {
        this.logs = this.logs.slice(0, 50)
      }
    },
    
    // Gamepad控制方法
    initGamepad() {
      // 检查浏览器是否支持Gamepad API
      if (!navigator.getGamepads) {
        console.warn('此浏览器不支持Gamepad API')
        this.addLog('此浏览器不支持Gamepad API', 'warning')
        return
      }
      
      // 监听gamepad连接事件
      window.addEventListener('gamepadconnected', this.onGamepadConnected)
      window.addEventListener('gamepaddisconnected', this.onGamepadDisconnected)
      
      // 检查是否已有连接的gamepad
      this.checkExistingGamepads()
      
      console.log('Gamepad系统已初始化')
      this.addLog('Gamepad系统已初始化', 'info')
    },
    
    checkExistingGamepads() {
      const gamepads = navigator.getGamepads()
      for (let i = 0; i < gamepads.length; i++) {
        if (gamepads[i]) {
          this.connectGamepad(i)
          break
        }
      }
    },
    
    onGamepadConnected(event) {
      console.log('Gamepad已连接:', event.gamepad.id)
      this.addLog(`Gamepad已连接: ${event.gamepad.id}`, 'success')
      this.connectGamepad(event.gamepad.index)
    },
    
    onGamepadDisconnected(event) {
      console.log('Gamepad已断开:', event.gamepad.id)
      this.addLog(`Gamepad已断开: ${event.gamepad.id}`, 'warning')
      this.disconnectGamepad()
    },
    
    connectGamepad(index) {
      this.gamepadConnected = true
      this.gamepadIndex = index
      this.lastButtonStates = {}
      
      // 开始gamepad输入循环
      this.startGamepadLoop()
      
      console.log(`Gamepad ${index} 已连接并开始监听`)
      this.addLog(`Gamepad ${index} 已连接并开始监听`, 'success')
    },
    
    disconnectGamepad() {
      this.gamepadConnected = false
      this.gamepadIndex = null
      this.lastGamepadInput = false // 重置手柄输入状态
      
      // 停止gamepad输入循环
      if (this.gamepadAnimationFrame) {
        cancelAnimationFrame(this.gamepadAnimationFrame)
        this.gamepadAnimationFrame = null
      }
      
      console.log('Gamepad已断开连接')
    },
    
    startGamepadLoop() {
      if (!this.gamepadConnected) return
      
      this.updateGamepad()
      this.gamepadAnimationFrame = requestAnimationFrame(() => this.startGamepadLoop())
    },
    
    updateGamepad() {
      if (!this.gamepadConnected || this.gamepadIndex === null) return
      
      const gamepads = navigator.getGamepads()
      const gamepad = gamepads[this.gamepadIndex]
      
      if (!gamepad) {
        this.disconnectGamepad()
        return
      }
      
      // 处理方向键/左摇杆输入
      this.handleGamepadMovement(gamepad)
      
      // 处理按钮输入
      this.handleGamepadButtons(gamepad)
    },
    
    handleGamepadMovement(gamepad) {
      const currentTime = Date.now()
      
      // 控制移动频率
      if (currentTime - this.lastMoveTime < this.moveInterval) {
        return
      }
      
      // 左摇杆轴 (axes[0] = X轴, axes[1] = Y轴)
      // 注意：摇杆的常态值可能不是0，需要进行中心化处理
      let leftStickX = gamepad.axes[0] || 0
      let leftStickY = gamepad.axes[1] || 0
      
      // 摇杆中心化处理：将常态值1映射为0（静止状态）
      // 如果摇杆常态下为1，则需要将其调整为以0为中心的范围
      if (Math.abs(leftStickY - 1) < 0.1) {
        leftStickY = 0 // 常态值1视为静止
      } else if (leftStickY > 1) {
        leftStickY = leftStickY - 1 // 将范围从[1,2]映射到[0,1]
      } else if (leftStickY < 1) {
        leftStickY = leftStickY - 1 // 将范围从[0,1]映射到[-1,0]
      }
      
      // X轴处理：默认值0为静止，左转为负值，右转为正值
       // 左右摇杆通常默认值就是0，不需要特殊的中心化处理
       // 如果摇杆值接近0，视为静止状态
       if (Math.abs(leftStickX) < 0.1) {
         leftStickX = 0 // 接近0时视为静止
       }
      
      // 方向键 (通常在axes[6]和axes[7]，或者buttons[12-15])
      const dpadX = gamepad.axes[6] || 0
      const dpadY = gamepad.axes[7] || 0
      
      // 合并摇杆和方向键输入
      let moveX = Math.abs(leftStickX) > this.deadZone ? leftStickX : dpadX
      let moveY = Math.abs(leftStickY) > this.deadZone ? leftStickY : dpadY
      
      // 降低摇杆灵敏度，需要更大的摇杆偏移才能触发移动
      const sensitivityThreshold = 0.5

      // 新增：获取轴5作为后退摇杆
      const backwardStick = gamepad.axes[5] === undefined ? 1 : gamepad.axes[5]
      const backwardStickActive = backwardStick < 0.95 // 留出死区，常态为1

      // 检查是否有任何手柄输入
      const hasGamepadInput = Math.abs(moveX) > sensitivityThreshold || Math.abs(moveY) > sensitivityThreshold || backwardStickActive
      
      // 检测手柄输入状态变化：从有输入变为无输入时停止小车
      if (this.lastGamepadInput && !hasGamepadInput) {
        this.setLinearVelocity(0)
        this.setAngularVelocity(0)
        this.addLog('手柄控制: 松开摇杆，小车停止', 'info')
      }
      
      // 只有在手柄有输入时才控制移动，避免覆盖其他控制方式
      if (hasGamepadInput) {
        // 处理前进/后退
        // 优先使用轴5作为后退摇杆
        if (backwardStickActive) {
          // 将轴5的值从 [1, -1] 映射到 [0, 1] 作为速度比例
          const backwardSpeed = (1 - backwardStick) / 2
          this.setLinearVelocity(-backwardSpeed * this.linearSpeed)
        } else if (Math.abs(moveY) > sensitivityThreshold) {
          // 使用左摇杆Y轴进行前进/后退
          // 摇杆向前推动时moveY为负值，对应前进（正速度）
          // 摇杆向后拉动时moveY为正值，对应后退（负速度）
          const linearVelocity = -moveY * this.linearSpeed
          this.setLinearVelocity(linearVelocity)
        } else {
          this.setLinearVelocity(0) // 停止前进/后退
        }
        
        // 处理左转/右转 (X轴) - 速度与摇杆偏移量成正比
        if (Math.abs(moveX) > sensitivityThreshold) {
          // 将摇杆值映射到角速度范围，确保线性响应
          const angularVelocity = -moveX * this.angularSpeed // 负号用于正确的转向方向
          this.setAngularVelocity(angularVelocity)
        } else {
          this.setAngularVelocity(0) // 停止转弯
        }
        
        this.lastMoveTime = currentTime
      }
      
      // 更新手柄输入状态
      this.lastGamepadInput = hasGamepadInput
    },
    
    handleGamepadButtons(gamepad) {
      // 按钮映射 (标准gamepad布局)
      const buttons = {
        A: 0,      // A按钮 (Xbox) / X按钮 (PlayStation)
        B: 1,      // B按钮 (Xbox) / Circle按钮 (PlayStation)
        X: 2,      // X按钮 (Xbox) / Square按钮 (PlayStation)
        Y: 3,      // Y按钮 (Xbox) / Triangle按钮 (PlayStation)
        LB: 4,     // 左肩键
        RB: 5,     // 右肩键
        LT: 6,     // 左扳机
        RT: 7,     // 右扳机
        SELECT: 8, // Select/Back按钮
        START: 9,  // Start按钮
        LS: 10,    // 左摇杆按下
        RS: 11,    // 右摇杆按下
        UP: 12,    // 方向键上
        DOWN: 13,  // 方向键下
        LEFT: 14,  // 方向键左
        RIGHT: 15  // 方向键右
      }
      
      // 检查按钮按下事件 (只在按钮刚按下时触发)
      Object.entries(buttons).forEach(([name, index]) => {
        const button = gamepad.buttons[index]
        const isPressed = button && button.pressed
        const wasPressed = this.lastButtonStates[name] || false
        
        // 按钮刚按下时触发
        if (isPressed && !wasPressed) {
          this.handleGamepadButtonPress(name)
        }
        
        this.lastButtonStates[name] = isPressed
      })
    },
    
    handleGamepadButtonPress(buttonName) {
      switch (buttonName) {
        case 'A':
          // A按钮 - 连接/断开小车
          this.toggleConnection()
          this.addLog('Gamepad: A按钮 - 连接/断开', 'info')
          break
          
        case 'B':
          // B按钮 - 停止移动
          this.sendStopCommand()
          this.addLog('Gamepad: B按钮 - 停止移动', 'info')
          break
          
        case 'Y':
          // Y按钮 - 返回首页
          this.goBack()
          this.addLog('Gamepad: Y按钮 - 返回首页', 'info')
          break
          
        case 'UP':
          // 方向键上 - 前进
          this.setLinearVelocity(this.linearSpeed)
          this.addLog('Gamepad: 方向键上 - 前进', 'info')
          break
          
        case 'DOWN':
          // 方向键下 - 后退
          this.setLinearVelocity(-this.linearSpeed)
          this.addLog('Gamepad: 方向键下 - 后退', 'info')
          break
          
        case 'LEFT':
          // 方向键左 - 左转
          this.setAngularVelocity(this.angularSpeed)
          this.addLog('Gamepad: 方向键左 - 左转', 'info')
          break
          
        case 'RIGHT':
          // 方向键右 - 右转
          this.setAngularVelocity(-this.angularSpeed)
          this.addLog('Gamepad: 方向键右 - 右转', 'info')
          break
      }
    },
    
    // 地图相关方法
    async loadMap() {
      this.mapLoading = true
      this.addLog('开始加载地图文件...', 'info')
      
      try {
        // 加载地图配置文件
        // 跳过YAML配置文件，使用默认配置
        this.addLog('使用默认地图配置...', 'info')
        this.mapResolution = 0.05
        this.mapOrigin = [-1.5, -1.44, 0]
        this.addLog(`使用默认配置 - 分辨率: ${this.mapResolution}, 原点: [${this.mapOrigin.join(', ')}]`, 'info')
        // 已使用默认配置，跳过YAML文件加载
        
        // 加载地图图像文件
        this.addLog('正在加载 PGM 图像文件...', 'info')
        const pgmResponse = await fetch('/src/components/map/map_01.pgm')
        if (!pgmResponse.ok) {
          throw new Error(`PGM文件加载失败: ${pgmResponse.status} ${pgmResponse.statusText}`)
        }
        const pgmArrayBuffer = await pgmResponse.arrayBuffer()
        this.addLog(`PGM文件大小: ${pgmArrayBuffer.byteLength} 字节`, 'info')
        await this.parseMapImage(pgmArrayBuffer)
        
        this.mapLoaded = true
        this.addLog('地图加载成功', 'success')
        
        // 等待下一帧再渲染，确保DOM已更新
        this.$nextTick(() => {
          this.renderMap()
        })
      } catch (error) {
        console.error('地图加载失败:', error)
        this.addLog('地图加载失败: ' + error.message, 'error')
        this.mapLoaded = false
      } finally {
        this.mapLoading = false
      }
    },
    
    parseMapConfig(yamlText) {
      const lines = yamlText.split('\n')
      for (const line of lines) {
        const trimmed = line.trim()
        if (trimmed.startsWith('resolution:')) {
          this.mapResolution = parseFloat(trimmed.split(':')[1].trim())
        } else if (trimmed.startsWith('origin:')) {
          const originStr = trimmed.split(':')[1].trim()
          this.mapOrigin = JSON.parse(originStr)
        }
      }
      this.addLog(`地图配置解析完成 - 分辨率: ${this.mapResolution}`, 'info')
    },
    
    async parseMapImage(arrayBuffer) {
      const uint8Array = new Uint8Array(arrayBuffer)
      let offset = 0
      
      // 读取PGM文件头
      let header = ''
      while (offset < uint8Array.length) {
        const char = String.fromCharCode(uint8Array[offset])
        header += char
        offset++
        
        if (header.includes('255\n')) {
          break
        }
      }
      
      // 解析头部信息
      const lines = header.trim().split('\n')
      const dimensions = lines[1].split(' ')
      this.mapWidth = parseInt(dimensions[0])
      this.mapHeight = parseInt(dimensions[1])
      
      // 读取图像数据
      const imageData = uint8Array.slice(offset)
      this.mapImageData = imageData
      
      this.addLog(`地图图像解析完成 - 尺寸: ${this.mapWidth}x${this.mapHeight}`, 'info')
    },
    
    renderMap() {
      if (!this.mapImageData || !this.$refs.mapCanvas) {
        return
      }
      
      const canvas = this.$refs.mapCanvas
      const ctx = canvas.getContext('2d')
      
      // 清空画布
      ctx.clearRect(0, 0, canvas.width, canvas.height)
      
      // 创建临时canvas来存储原始图像
      const tempCanvas = document.createElement('canvas')
      tempCanvas.width = this.mapWidth
      tempCanvas.height = this.mapHeight
      const tempCtx = tempCanvas.getContext('2d')
      
      // 创建ImageData对象
      const imageData = tempCtx.createImageData(this.mapWidth, this.mapHeight)
      
      // 将PGM数据转换为RGBA格式
      for (let i = 0; i < this.mapImageData.length; i++) {
        const grayValue = this.mapImageData[i]
        const pixelIndex = i * 4
        
        // 根据灰度值设置颜色
        if (grayValue === 255) {
          // 白色 - 自由空间
          imageData.data[pixelIndex] = 255     // R
          imageData.data[pixelIndex + 1] = 255 // G
          imageData.data[pixelIndex + 2] = 255 // B
          imageData.data[pixelIndex + 3] = 255 // A
        } else if (grayValue === 0) {
          // 黑色 - 障碍物
          imageData.data[pixelIndex] = 0       // R
          imageData.data[pixelIndex + 1] = 0   // G
          imageData.data[pixelIndex + 2] = 0   // B
          imageData.data[pixelIndex + 3] = 255 // A
        } else {
          // 灰色 - 未知区域
          imageData.data[pixelIndex] = 128     // R
          imageData.data[pixelIndex + 1] = 128 // G
          imageData.data[pixelIndex + 2] = 128 // B
          imageData.data[pixelIndex + 3] = 255 // A
        }
      }
      
      // 将图像数据绘制到临时画布上
      tempCtx.putImageData(imageData, 0, 0)
      
      // 计算缩放比例以适应画布
      const scaleX = canvas.width / this.mapWidth
      const scaleY = canvas.height / this.mapHeight
      const scale = Math.min(scaleX, scaleY)
      
      // 计算居中位置
      const scaledWidth = this.mapWidth * scale
      const scaledHeight = this.mapHeight * scale
      const offsetX = (canvas.width - scaledWidth) / 2
      const offsetY = (canvas.height - scaledHeight) / 2
      
      // 将临时画布的内容缩放绘制到主画布上
      ctx.drawImage(tempCanvas, 0, 0, this.mapWidth, this.mapHeight, offsetX, offsetY, scaledWidth, scaledHeight)
      
      this.addLog('地图渲染完成', 'success')
    },
    
    resetMapView() {
      this.mapScale = 1
      this.mapOffsetX = 0
      this.mapOffsetY = 0
      this.renderMap()
      this.addLog('地图视图已重置', 'info')
    },
    
    // 地图文件管理方法
    showMapSelector() {
      this.$refs.mapFileInput.click()
    },
    
    async handleMapFileUpload(event) {
      const files = Array.from(event.target.files)
      if (files.length === 0) return
      
      this.addLog(`选择了 ${files.length} 个文件`, 'info')
      
      let pgmFile = null
      let yamlFile = null
      
      // 分类文件
      for (const file of files) {
        if (file.name.endsWith('.pgm')) {
          pgmFile = file
        } else if (file.name.endsWith('.yaml') || file.name.endsWith('.yml')) {
          yamlFile = file
        }
      }
      
      if (!pgmFile || !yamlFile) {
        this.addLog('请同时选择 .pgm 和 .yaml 文件', 'error')
        return
      }
      
      try {
        // 读取文件内容
        const mapName = pgmFile.name.replace('.pgm', '')
        const pgmData = await this.readFileAsArrayBuffer(pgmFile)
        const yamlData = await this.readFileAsText(yamlFile)
        
        // 存储地图数据
        this.uploadedMaps.set(mapName, {
          pgmData,
          yamlData,
          pgmFile: pgmFile.name,
          yamlFile: yamlFile.name
        })
        
        // 添加到可用地图列表
        if (!this.availableMaps.includes(mapName)) {
          this.availableMaps.push(mapName)
        }
        
        this.addLog(`地图 "${mapName}" 上传成功`, 'success')
        
        // 自动切换到新上传的地图
        await this.switchToMap(mapName)
        
      } catch (error) {
        console.error('文件上传失败:', error)
        this.addLog('文件上传失败: ' + error.message, 'error')
      }
      
      // 清空文件输入
      event.target.value = ''
    },
    
    async switchToMap(mapName) {
      this.currentMapName = mapName
      this.addLog(`切换到地图: ${mapName}`, 'info')
      
      if (this.uploadedMaps.has(mapName)) {
        // 使用上传的地图
        await this.loadUploadedMap(mapName)
      } else {
        // 使用默认地图
        await this.loadMap()
      }
    },
    
    async loadUploadedMap(mapName) {
      const mapData = this.uploadedMaps.get(mapName)
      if (!mapData) {
        this.addLog(`地图 "${mapName}" 不存在`, 'error')
        return
      }
      
      this.mapLoading = true
      this.addLog(`开始加载上传的地图: ${mapName}`, 'info')
      
      try {
        // 解析YAML配置
        this.parseMapConfig(mapData.yamlData)
        
        // 解析PGM图像
        await this.parseMapImage(mapData.pgmData)
        
        this.mapLoaded = true
        this.addLog('上传地图加载成功', 'success')
        
        // 渲染地图
        this.$nextTick(() => {
          this.renderMap()
        })
      } catch (error) {
        console.error('上传地图加载失败:', error)
        this.addLog('上传地图加载失败: ' + error.message, 'error')
        this.mapLoaded = false
      } finally {
        this.mapLoading = false
      }
    },
    
    readFileAsArrayBuffer(file) {
      return new Promise((resolve, reject) => {
        const reader = new FileReader()
        reader.onload = () => resolve(reader.result)
        reader.onerror = () => reject(new Error('文件读取失败'))
        reader.readAsArrayBuffer(file)
      })
    },
    
    readFileAsText(file) {
      return new Promise((resolve, reject) => {
        const reader = new FileReader()
        reader.onload = () => resolve(reader.result)
        reader.onerror = () => reject(new Error('文件读取失败'))
        reader.readAsText(file)
      })
    },
    
    // 地图监控相关方法
    connectMapMonitor() {
      if (this.mapMonitorWebSocket) {
        this.mapMonitorWebSocket.close()
      }
      
      this.addLog('正在连接地图监控服务...', 'info')
      
      try {
        this.mapMonitorWebSocket = new WebSocket(this.mapMonitorUrl)
        
        this.mapMonitorWebSocket.onopen = () => {
          this.mapMonitorConnected = true
          this.addLog('地图监控服务连接成功', 'success')
        }
        
        this.mapMonitorWebSocket.onmessage = (event) => {
          this.handleMapMonitorMessage(event)
        }
        
        this.mapMonitorWebSocket.onclose = () => {
          this.mapMonitorConnected = false
          this.addLog('地图监控服务连接已断开', 'warning')
          
          // 自动重连
          if (this.autoReloadMap) {
            setTimeout(() => {
              this.connectMapMonitor()
            }, 5000)
          }
        }
        
        this.mapMonitorWebSocket.onerror = (error) => {
          this.addLog('地图监控服务连接错误', 'error')
          console.error('Map monitor WebSocket error:', error)
        }
      } catch (error) {
        this.addLog('地图监控服务连接失败: ' + error.message, 'error')
      }
    },
    
    disconnectMapMonitor() {
      if (this.mapMonitorWebSocket) {
        this.mapMonitorWebSocket.close()
        this.mapMonitorWebSocket = null
        this.mapMonitorConnected = false
        this.addLog('地图监控服务已断开', 'info')
      }
    },
    
    handleMapMonitorMessage(event) {
      try {
        const data = JSON.parse(event.data)
        
        // 调试信息：打印接收到的main.py消息详情
        console.log('[DEBUG] 接收到main.py地图监控消息:', {
          原始消息: event.data,
          解析后数据: data,
          事件类型: data.event,
          文件路径: data.file_path,
          文件大小: data.file_size,
          时间戳: data.timestamp,
          主机名: data.hostname
        })
        
        this.addLog(`[DEBUG] 接收到main.py消息 - 事件: ${data.event}, 文件: ${data.file_path}, 大小: ${data.file_size}字节`, 'info')
        this.addLog(`地图监控通知: ${data.event} - ${data.file_path}`, 'info')
        
        // 更新最后更新时间
        this.lastMapUpdateTime = new Date(data.timestamp * 1000)
        
        // 处理不同类型的消息，如果包含地图数据则直接加载
        if (data.event === 'initial' || data.event === 'created' || data.event === 'modified' || data.event === 'periodic') {
          if (data.file_data) {
            // 如果消息中包含文件数据，直接从消息加载地图
            this.addLog('检测到地图数据，正在从WebSocket消息加载...', 'info')
            this.loadMapFromMessage(data)
          } else if (this.autoReloadMap && (data.event === 'modified' || data.event === 'created')) {
            // 如果没有文件数据但启用了自动重载，则重新加载本地地图
            this.addLog('检测到地图文件变化，正在自动重新加载...', 'info')
            setTimeout(() => {
              this.loadMap()
            }, 1000) // 延迟1秒以确保文件写入完成
          }
        }
      } catch (error) {
        this.addLog('解析地图监控消息失败: ' + error.message, 'error')
      }
    },
    
    // 新增：从WebSocket消息中加载地图数据
    loadMapFromMessage(message) {
      if (this.mapLoading) return
      this.mapLoading = true
      
      try {
        // 从消息中获取文件数据（hex格式）
        const fileData = message.file_data
        if (!fileData) {
          throw new Error('文件数据为空')
        }
        
        this.addLog('正在解析接收到的地图数据...', 'info')
        
        // 将hex数据转换为二进制
        const binaryData = this.hexToBinary(fileData)
        
        // 解析PGM格式（支持ASCII和Binary）
        const pgmData = this.parsePGM(binaryData)
        if (!pgmData) {
          throw new Error('无法解析PGM格式，可能是文件损坏或格式不支持')
        }
        
        this.addLog(`成功解析PGM地图: ${pgmData.width}x${pgmData.height}，最大灰度值: ${pgmData.maxVal}`, 'success')
        
        // 更新地图尺寸
        this.mapWidth = pgmData.width
        this.mapHeight = pgmData.height
        
        // 创建ImageData对象
        const imageData = new ImageData(pgmData.width, pgmData.height)
        const data = imageData.data
        
        // 填充像素数据
        for (let y = 0; y < pgmData.height; y++) {
          for (let x = 0; x < pgmData.width; x++) {
            const index = (y * pgmData.width + x) * 4
            const pixelValue = pgmData.pixels[y * pgmData.width + x]
            
            // 灰度图转RGB (注意: PGM中的0表示黑色，255表示白色，这里做了反转处理)
            const invertedValue = 255 - pixelValue // 反转黑白
            data[index] = invertedValue     // R
            data[index + 1] = invertedValue // G
            data[index + 2] = invertedValue // B
            data[index + 3] = 255           // A
          }
        }
        
        // 保存地图图像数据
        this.mapImageData = imageData
        
        // 渲染地图到画布
        this.renderMapFromImageData(imageData)
        
        this.mapLoaded = true
        this.addLog('地图加载完成', 'success')
        
      } catch (error) {
        this.addLog('处理地图数据失败: ' + error.message, 'error')
      } finally {
        this.mapLoading = false
      }
    },
    
    // 新增：hex字符串转二进制
    hexToBinary(hex) {
      // 移除所有非十六进制字符
      hex = hex.replace(/[^0-9a-fA-F]/g, '')
      if (hex.length % 2 !== 0) {
        throw new Error('hex数据长度必须为偶数')
      }
      
      const binary = new Uint8Array(hex.length / 2)
      for (let i = 0; i < binary.length; i++) {
        const byte = hex.substr(i * 2, 2)
        if (!byte) throw new Error('hex数据不完整')
        binary[i] = parseInt(byte, 16)
      }
      return binary
    },
    
    // 新增：解析PGM格式
    parsePGM(data) {
      let index = 0
      
      // 读取文件头（P5为Binary PGM，P2为ASCII PGM）
      const magicNumber = String.fromCharCode(data[index]) + String.fromCharCode(data[index + 1])
      index += 2
      
      if (magicNumber !== 'P5' && magicNumber !== 'P2') {
        console.error('不是有效的PGM格式，magicNumber:', magicNumber)
        return null
      }
      
      // 跳过注释
      while (data[index] === 35) { // '#'
        while (index < data.length && data[index] !== 10) { // 换行符
          index++
        }
        index++ // 跳过换行符
      }
      
      // 读取宽度和高度
      let width = 0, height = 0
      let numChars = 0
      
      // 读取宽度
      while (index < data.length && (data[index] === 32 || data[index] === 10 || data[index] === 9)) {
        index++ // 跳过空格、换行、制表符
      }
      while (index < data.length && data[index] >= 48 && data[index] <= 57) { // 数字
        width = width * 10 + (data[index] - 48)
        index++
        numChars++
      }
      if (numChars === 0) return null
      
      numChars = 0
      // 读取高度
      while (index < data.length && (data[index] === 32 || data[index] === 10 || data[index] === 9)) {
        index++
      }
      while (index < data.length && data[index] >= 48 && data[index] <= 57) {
        height = height * 10 + (data[index] - 48)
        index++
        numChars++
      }
      if (numChars === 0) return null
      
      // 读取最大灰度值
      while (index < data.length && (data[index] === 32 || data[index] === 10 || data[index] === 9)) {
        index++
      }
      let maxVal = 0
      numChars = 0
      while (index < data.length && data[index] >= 48 && data[index] <= 57) {
        maxVal = maxVal * 10 + (data[index] - 48)
        index++
        numChars++
      }
      if (numChars === 0) return null
      
      // 跳过换行符
      while (index < data.length && (data[index] === 10 || data[index] === 13)) {
        index++
      }
      
      // 读取像素数据
      const pixelCount = width * height
      const pixels = new Uint8Array(pixelCount)
      
      if (magicNumber === 'P5') {
        // Binary PGM
        for (let i = 0; i < pixelCount && index < data.length; i++) {
          if (maxVal > 255) {
            // 16-bit PGM (uncommon)
            const highByte = data[index++]
            const lowByte = data[index++]
            pixels[i] = Math.round(((highByte << 8) | lowByte) * 255 / maxVal)
          } else {
            // 8-bit PGM
            pixels[i] = data[index++]
          }
        }
      } else {
        // ASCII PGM
        let value = 0
        let readingNumber = false
        let pixelIndex = 0
        
        for (; index < data.length && pixelIndex < pixelCount; index++) {
          const charCode = data[index]
          
          if (charCode >= 48 && charCode <= 57) { // 数字
            value = value * 10 + (charCode - 48)
            readingNumber = true
          } else if (readingNumber) {
            // 非数字字符，结束当前数字的读取
            pixels[pixelIndex++] = Math.round(value * 255 / maxVal)
            value = 0
            readingNumber = false
            
            // 如果是换行符或空格，继续读取下一个数字
            if (charCode === 10 || charCode === 32 || charCode === 9) {
              continue
            }
          }
        }
        
        // 处理最后一个数字
        if (readingNumber && pixelIndex < pixelCount) {
          pixels[pixelIndex] = Math.round(value * 255 / maxVal)
        }
      }
      
      // 验证是否读取了足够的像素
      if (pixels.length < pixelCount) {
        console.warn(`警告: 读取的像素数量不足 (${pixels.length}/${pixelCount})`)
      }
      
      return {
        width: width,
        height: height,
        maxVal: maxVal,
        pixels: pixels
      }
    },
    
    // 新增：从ImageData渲染地图到画布
    renderMapFromImageData(imageData) {
      const canvas = this.$refs.mapCanvas
      if (!canvas) return
      
      const ctx = canvas.getContext('2d')
      
      // 创建临时画布并绘制
      const tempCanvas = document.createElement('canvas')
      tempCanvas.width = imageData.width
      tempCanvas.height = imageData.height
      const tempCtx = tempCanvas.getContext('2d')
      tempCtx.putImageData(imageData, 0, 0)
      
      // 计算缩放比例以适应画布
      const scaleX = canvas.width / imageData.width
      const scaleY = canvas.height / imageData.height
      const scale = Math.min(scaleX, scaleY) * 0.95 // 稍微缩小一点，留出边距
      
      // 计算居中位置
      const scaledWidth = imageData.width * scale
      const scaledHeight = imageData.height * scale
      const offsetX = (canvas.width - scaledWidth) / 2
      const offsetY = (canvas.height - scaledHeight) / 2
      
      // 清除画布并绘制地图
      ctx.clearRect(0, 0, canvas.width, canvas.height)
      ctx.drawImage(tempCanvas, offsetX, offsetY, scaledWidth, scaledHeight)
      
      // 清理临时画布
      tempCanvas.remove()
    },
    
    toggleMapMonitor() {
      if (this.autoReloadMap) {
        if (!this.mapMonitorConnected) {
          this.connectMapMonitor()
        }
        this.addLog('地图自动更新已启用', 'success')
      } else {
        this.addLog('地图自动更新已禁用', 'info')
      }
    },
    
    formatTime(date) {
      if (!date) return ''
      return date.toLocaleTimeString('zh-CN', {
        hour: '2-digit',
        minute: '2-digit',
        second: '2-digit'
      })
    }
  }
}
</script>

<style scoped>
.control-mode {
  width: 100%;
  height: 100vh;
  min-height: 100vh;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  padding: 20px;
  box-sizing: border-box;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  overflow-y: auto;
}

.header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
  background: rgba(255, 255, 255, 0.1);
  padding: 15px 20px;
  border-radius: 10px;
  backdrop-filter: blur(10px);
}

.header-left {
  display: flex;
  align-items: center;
  gap: 15px;
}

.header-right {
  display: flex;
  align-items: center;
  gap: 15px;
}

.connection-status {
  display: flex;
  align-items: center;
  gap: 8px;
}

.connection-indicator {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  transition: all 0.3s ease;
}

.connection-indicator.connected {
  background-color: #00B42A;
  box-shadow: 0 0 10px rgba(0, 180, 42, 0.5);
}

.connection-indicator.disconnected {
  background-color: #F53F3F;
  box-shadow: 0 0 10px rgba(245, 63, 63, 0.5);
}

.status-text {
  color: white;
  font-size: 14px;
  font-weight: 500;
}

.connect-btn {
  padding: 8px 16px;
  border: none;
  border-radius: 8px;
  color: white;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.3s ease;
  display: flex;
  align-items: center;
  gap: 6px;
}

.connect-btn.disconnected {
  background: linear-gradient(45deg, #165DFF, #1976D2);
}

.connect-btn.connected {
  background: linear-gradient(45deg, #6B7280, #4B5563);
}

.connect-btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
}

.back-btn {
  background: rgba(255, 255, 255, 0.2);
  border: none;
  color: white;
  padding: 8px 16px;
  border-radius: 5px;
  cursor: pointer;
  font-size: 14px;
  transition: all 0.3s ease;
}

.back-btn:hover {
  background: rgba(255, 255, 255, 0.3);
  transform: translateY(-2px);
}

.header h1 {
  color: white;
  margin: 0;
  font-size: 24px;
}

.control-container {
  display: grid;
  grid-template-columns: 2fr 1fr;
  gap: 20px;
  height: calc(100vh - 120px);
}

.video-panel {
  background: rgba(255, 255, 255, 0.1);
  border-radius: 15px;
  padding: 20px;
  backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, 0.2);
}

.video-panel h3 {
  color: white;
  margin: 0 0 15px 0;
  font-size: 18px;
}

.video-container {
  position: relative;
  width: 100%;
  height: calc(100% - 50px);
  background: #000;
  border-radius: 10px;
  overflow: hidden;
}

.video-stream {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

.video-iframe {
  width: 100%;
  height: 100%;
  border: none;
  border-radius: 10px;
}

.control-panel {
  background: rgba(255, 255, 255, 0.1);
  border-radius: 15px;
  padding: 20px;
  backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, 0.2);
  overflow-y: auto;
}

.control-panel h3 {
  color: white;
  margin: 0 0 20px 0;
  font-size: 18px;
}

.control-panel h4 {
  color: white;
  margin: 0 0 15px 0;
  font-size: 16px;
  text-align: center;
}

/* ROS2控制器样式 */
.ros-controller {
  margin-bottom: 30px;
  padding: 20px;
  background: rgba(255, 255, 255, 0.05);
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, 0.1);
}

.direction-controls-grid {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

.control-row {
  display: flex;
  gap: 10px;
  align-items: center;
}

.ros-direction-btn {
  width: 80px;
  height: 50px;
  border: none;
  border-radius: 8px;
  background: linear-gradient(45deg, #165DFF, #1976D2);
  color: white;
  font-size: 14px;
  font-weight: bold;
  cursor: pointer;
  transition: all 0.3s ease;
  display: flex;
  align-items: center;
  justify-content: center;
  user-select: none;
}

.ros-direction-btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 5px 15px rgba(22, 93, 255, 0.4);
}

.ros-direction-btn:active {
  transform: translateY(0);
}

.ros-direction-btn.stop {
  background: linear-gradient(45deg, #F53F3F, #d32f2f);
  width: 60px;
}

.ros-direction-btn.stop:hover {
  box-shadow: 0 5px 15px rgba(245, 63, 63, 0.4);
}

/* 虚拟摇杆样式 */
.joystick-controller {
  margin-bottom: 30px;
  padding: 20px;
  background: rgba(255, 255, 255, 0.05);
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, 0.1);
}

/* 键盘控制样式 */
.keyboard-controller {
  margin-bottom: 20px;
  padding: 15px;
  background: rgba(255, 255, 255, 0.1);
  border-radius: 10px;
  backdrop-filter: blur(10px);
}

.keyboard-instructions {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 10px;
  margin-bottom: 10px;
}

.key-instruction {
  display: flex;
  align-items: center;
  gap: 8px;
}

.key-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 30px;
  height: 30px;
  background: rgba(255, 255, 255, 0.2);
  border: 2px solid rgba(255, 255, 255, 0.3);
  border-radius: 6px;
  color: white;
  font-weight: bold;
  font-size: 12px;
  text-align: center;
  padding: 0 8px;
}

.key-description {
  color: white;
  font-size: 14px;
}

.keyboard-note {
  color: rgba(255, 255, 255, 0.8);
  font-size: 12px;
  margin: 10px 0 0 0;
  font-style: italic;
}

/* 手柄控制样式 */
.gamepad-controller {
  margin-bottom: 20px;
  padding: 15px;
  background: rgba(255, 255, 255, 0.1);
  border-radius: 10px;
  backdrop-filter: blur(10px);
}

.gamepad-instructions {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px;
  margin-bottom: 10px;
}

.gamepad-instruction {
  display: flex;
  align-items: center;
  gap: 8px;
}

.gamepad-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 40px;
  height: 30px;
  background: rgba(34, 150, 243, 0.3);
  border: 2px solid rgba(34, 150, 243, 0.5);
  border-radius: 6px;
  color: white;
  font-weight: bold;
  font-size: 11px;
  text-align: center;
  padding: 0 8px;
}

.gamepad-description {
  color: white;
  font-size: 14px;
}

.gamepad-note {
  color: rgba(255, 255, 255, 0.8);
  font-size: 12px;
  margin: 10px 0 0 0;
  font-style: italic;
  display: flex;
  align-items: center;
  gap: 5px;
}

.joystick-area {
  width: 200px;
  height: 200px;
  margin: 0 auto;
  background: rgba(255, 255, 255, 0.1);
  border-radius: 50%;
  position: relative;
  overflow: hidden;
  cursor: grab;
}

.joystick-area:active {
  cursor: grabbing;
}

.joystick-knob {
  width: 60px;
  height: 60px;
  background: linear-gradient(45deg, #165DFF, #1976D2);
  border-radius: 50%;
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
  cursor: grab;
  transition: all 0.1s ease;
}

.joystick-knob:active {
  cursor: grabbing;
}

.joystick-knob.scale-110 {
  transform: translate(-50%, -50%) scale(1.1);
}

/* 速度控制样式 */
.speed-controls {
  margin-bottom: 30px;
  padding: 20px;
  background: rgba(255, 255, 255, 0.05);
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, 0.1);
}

.speed-control {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 15px;
  color: white;
}

.speed-control:last-child {
  margin-bottom: 0;
}

.speed-control label {
  font-size: 14px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.speed-value {
  font-weight: bold;
  color: #165DFF;
}

.speed-slider {
  width: 100%;
  height: 6px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.2);
  outline: none;
  cursor: pointer;
  appearance: none;
}

.speed-slider::-webkit-slider-thumb {
  appearance: none;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: #165DFF;
  cursor: pointer;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
}

.speed-slider::-moz-range-thumb {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: #165DFF;
  cursor: pointer;
  border: none;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
}

/* 原有样式保持不变 */
.control-buttons {
  margin-bottom: 30px;
}

.control-btn {
  width: 100%;
  padding: 12px;
  border: none;
  border-radius: 8px;
  background: linear-gradient(45deg, #2196F3, #1976D2);
  color: white;
  font-size: 16px;
  font-weight: bold;
  cursor: pointer;
  transition: all 0.3s ease;
}

.control-btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 5px 15px rgba(33, 150, 243, 0.4);
}

.status-info {
  margin-bottom: 20px;
}

.status-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 8px 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}

.status-item:last-child {
  border-bottom: none;
}

.status-label {
  color: rgba(255, 255, 255, 0.8);
  font-size: 14px;
}

.status-value {
  color: white;
  font-weight: 500;
  font-size: 14px;
}

.status-value.connected {
  color: #00B42A;
}

.status-value.disconnected {
  color: #F53F3F;
}

.status-value.moving {
  color: #165DFF;
}

.status-value.stopped {
  color: #9E9E9E;
}

.log-panel {
  background: rgba(255, 255, 255, 0.05);
  border-radius: 10px;
  padding: 15px;
  border: 1px solid rgba(255, 255, 255, 0.1);
}

.log-panel h4 {
  color: white;
  margin: 0 0 10px 0;
  font-size: 14px;
}

.log-container {
  max-height: 200px;
  overflow-y: auto;
}

.log-item {
  display: flex;
  gap: 10px;
  padding: 5px 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
  font-size: 12px;
}

.log-item:last-child {
  border-bottom: none;
}

.log-time {
  color: rgba(255, 255, 255, 0.6);
  min-width: 80px;
}

.log-message {
  color: white;
  flex: 1;
}

.log-item.success .log-message {
  color: #00B42A;
}

.log-item.error .log-message {
  color: #F53F3F;
}

.log-item.warning .log-message {
  color: #FF7D00;
}

.log-item.info .log-message {
  color: #36CFC9;
}

/* 加载和连接控制样式 */
.loading-overlay {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.8);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  color: white;
  gap: 15px;
}

.loading-spinner {
  width: 40px;
  height: 40px;
  border: 4px solid rgba(255, 255, 255, 0.3);
  border-top: 4px solid white;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

.retry-btn, .switch-mode-btn, .update-btn {
  padding: 8px 16px;
  border: none;
  border-radius: 5px;
  background: rgba(255, 255, 255, 0.2);
  color: white;
  cursor: pointer;
  font-size: 12px;
  transition: all 0.3s ease;
}

.retry-btn:hover, .switch-mode-btn:hover, .update-btn:hover {
  background: rgba(255, 255, 255, 0.3);
  transform: translateY(-2px);
}

.connection-controls {
  margin-top: 15px;
}

.url-input {
  display: flex;
  align-items: center;
  gap: 10px;
  color: white;
}

.url-input label {
  font-size: 14px;
  min-width: 80px;
}

.url-field {
  flex: 1;
  padding: 8px 12px;
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 5px;
  background: rgba(255, 255, 255, 0.1);
  color: white;
  font-size: 12px;
}

.url-field::placeholder {
  color: rgba(255, 255, 255, 0.5);
}

/* 响应式设计 */
@media (max-width: 768px) {
  .control-container {
    grid-template-columns: 1fr;
    grid-template-rows: 1fr 1fr;
  }
  
  .ros-direction-btn {
    width: 70px;
    height: 45px;
    font-size: 12px;
  }
  
  .ros-direction-btn.stop {
    width: 55px;
  }
  
  .joystick-area {
    width: 150px;
    height: 150px;
  }
  
  .joystick-knob {
    width: 45px;
    height: 45px;
  }
}

@media (max-width: 480px) {
  .ros-direction-btn {
    width: 60px;
    height: 40px;
    font-size: 11px;
  }
  
  .ros-direction-btn.stop {
    width: 50px;
  }
  
  .joystick-area {
    width: 120px;
    height: 120px;
  }
  
  .joystick-knob {
    width: 35px;
    height: 35px;
  }
}

/* 地图选择器样式 */
.map-selector {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
}

.map-selector label {
  color: white;
  font-size: 14px;
  font-weight: 500;
  min-width: 80px;
}

.map-select {
  padding: 8px 12px;
  border: 2px solid rgba(255, 255, 255, 0.3);
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.1);
  color: white;
  font-size: 14px;
  min-width: 120px;
  transition: all 0.3s ease;
}

.map-select:focus {
  outline: none;
  border-color: rgba(34, 150, 243, 0.8);
  background: rgba(255, 255, 255, 0.15);
}

.map-select option {
  background: #2c3e50;
  color: white;
  padding: 8px;
}

.map-controls {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 15px;
}

.map-btn {
  padding: 10px 15px;
  border: 2px solid rgba(255, 255, 255, 0.3);
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.1);
  color: white;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.3s ease;
  min-height: 40px;
}

.map-btn:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.2);
  border-color: rgba(255, 255, 255, 0.5);
  transform: translateY(-2px);
}

.map-btn:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

@media (min-width: 768px) {
  .map-controls {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
  }
  
  .map-selector {
    margin-bottom: 0;
    margin-right: 15px;
  }
}

/* 地图监控开关样式 */
.map-monitor-control {
  margin-bottom: 10px;
}

.monitor-switch {
  display: flex;
  align-items: center;
  gap: 10px;
  cursor: pointer;
  user-select: none;
}

.monitor-switch input[type="checkbox"] {
  display: none;
}

.switch-slider {
  position: relative;
  width: 50px;
  height: 24px;
  background: rgba(255, 255, 255, 0.2);
  border-radius: 12px;
  transition: all 0.3s ease;
  border: 2px solid rgba(255, 255, 255, 0.3);
}

.switch-slider::before {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  background: white;
  border-radius: 50%;
  transition: all 0.3s ease;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}

.monitor-switch input[type="checkbox"]:checked + .switch-slider {
  background: rgba(34, 150, 243, 0.8);
  border-color: rgba(34, 150, 243, 1);
}

.monitor-switch input[type="checkbox"]:checked + .switch-slider::before {
  transform: translateX(26px);
  background: #165DFF;
}

.switch-label {
  color: white;
  font-size: 14px;
  font-weight: 500;
}

.monitor-switch:hover .switch-slider {
  background: rgba(255, 255, 255, 0.3);
}

.monitor-switch:hover input[type="checkbox"]:checked + .switch-slider {
  background: rgba(34, 150, 243, 0.9);
}

@media (min-width: 768px) {
  .map-monitor-control {
    margin-bottom: 0;
    margin-right: 15px;
  }
}

/* 配置按钮样式 */
.config-btn {
  padding: 8px 16px;
  margin-right: 10px;
  background: rgba(255, 255, 255, 0.1);
  border: 2px solid rgba(255, 255, 255, 0.3);
  border-radius: 6px;
  color: white;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.3s ease;
  display: flex;
  align-items: center;
  gap: 6px;
}

.config-btn:hover {
  background: rgba(255, 255, 255, 0.2);
  border-color: rgba(255, 255, 255, 0.5);
  transform: translateY(-2px);
}

.config-btn i {
  font-size: 16px;
}

/* 配置弹窗样式 */
.modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.7);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000;
  backdrop-filter: blur(5px);
}

.modal-content {
  background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
  border-radius: 12px;
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
  width: 90%;
  max-width: 500px;
  max-height: 90vh;
  overflow-y: auto;
  border: 2px solid rgba(255, 255, 255, 0.1);
}

.modal-header {
  padding: 20px 24px 16px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.modal-header h3 {
  margin: 0;
  color: white;
  font-size: 20px;
  font-weight: 600;
}

.close {
  background: none;
  border: none;
  color: rgba(255, 255, 255, 0.7);
  font-size: 28px;
  cursor: pointer;
  padding: 0;
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  transition: all 0.3s ease;
}

.close:hover {
  background: rgba(255, 255, 255, 0.1);
  color: white;
}

.modal-body {
  padding: 24px;
}

.config-group {
  margin-bottom: 20px;
}

.config-group:last-child {
  margin-bottom: 0;
}

.config-group label {
  display: block;
  color: white;
  font-size: 14px;
  font-weight: 500;
  margin-bottom: 8px;
}

.config-group input {
  width: 100%;
  padding: 12px 16px;
  border: 2px solid rgba(255, 255, 255, 0.2);
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.1);
  color: white;
  font-size: 14px;
  transition: all 0.3s ease;
  box-sizing: border-box;
}

.config-group input:focus {
  outline: none;
  border-color: rgba(34, 150, 243, 0.8);
  background: rgba(255, 255, 255, 0.15);
  box-shadow: 0 0 0 3px rgba(34, 150, 243, 0.2);
}

.config-group input::placeholder {
  color: rgba(255, 255, 255, 0.5);
}

.modal-footer {
  padding: 16px 24px 24px;
  display: flex;
  justify-content: flex-end;
  gap: 12px;
  border-top: 1px solid rgba(255, 255, 255, 0.1);
}

.btn-cancel,
.btn-save {
  padding: 10px 20px;
  border: 2px solid;
  border-radius: 6px;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.3s ease;
  min-width: 80px;
}

.btn-cancel {
  background: transparent;
  border-color: rgba(255, 255, 255, 0.3);
  color: rgba(255, 255, 255, 0.8);
}

.btn-cancel:hover {
  background: rgba(255, 255, 255, 0.1);
  border-color: rgba(255, 255, 255, 0.5);
  color: white;
}

.btn-save {
  background: rgba(34, 150, 243, 0.8);
  border-color: rgba(34, 150, 243, 1);
  color: white;
}

.btn-save:hover {
  background: rgba(34, 150, 243, 1);
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(34, 150, 243, 0.3);
}

@media (max-width: 768px) {
  .modal-content {
    width: 95%;
    margin: 20px;
  }
  
  .modal-header,
  .modal-body,
  .modal-footer {
    padding-left: 16px;
    padding-right: 16px;
  }
  
  .modal-footer {
    flex-direction: column;
  }
  
  .btn-cancel,
  .btn-save {
    width: 100%;
  }
}
</style>